package org.bud.rung.config;

import org.assertj.core.util.Lists;
import org.bud.rung.bit.Job;
import org.bud.rung.bit.JobKit;
import org.bud.rung.bit.JobMethod;
import org.bud.rung.constant.Constant;
import org.bud.rung.netty.client.NettyClient;
import org.bud.rung.netty.server.NettyServer;
import org.bud.rung.netty.util.Crones;
import org.bud.rung.netty.util.Crons;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author shaun
 * @date 2024/8/10
 * @instruction
 */
@Configuration
public class JobConfig implements SmartInitializingSingleton, ApplicationContextAware {
    private static final Integer MAX_LIMIT_SECONDS = 5;
    private final Object lock = new Object();

    private final AtomicBoolean first = new AtomicBoolean(true);

    @Override
    public void afterSingletonsInstantiated() {
        NettyServer.getInstance().start();
        NettyClient.getInstance().start();
        new Thread(() -> {
            Constant.jobMethodCache.clear();
            String[] beanNames = this.context.getBeanNamesForAnnotation(JobKit.class);
            Lists.newArrayList(beanNames)
                    .stream()
                    .forEach(o -> {
                        Object bean = this.context.getBean(o);
                        ReflectionUtils.doWithLocalMethods(bean.getClass(), m -> {
                            Job annotation = m.getAnnotation(Job.class);
                            if(annotation != null){
                                Constant.jobMethodCache.put(annotation.value(),
                                        JobMethod.builder().job(annotation)
                                                .jobName(annotation.value())
                                                .method(m).bean(bean).build());
                            }
                        });
                    });
            synchronized (lock) {
                lock.notify();
            }
        }).start();

        new Thread(() -> {
            boolean first = true;
            while(true) {
                if(first){
                    first = false;
                    synchronized (lock){
                        try {
                            lock.wait(80);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
                LocalDateTime now = LocalDateTime.now();
                Constant.jobMethodCache.entrySet()
                        .stream().forEach(o -> {
                            JobMethod method = Constant.jobMethodCache.get(o.getKey());
                            if(Objects.isNull(method.getLast())){
                                method.setLast(Crones.next(method.getJob().cron(),now,true));
                            }
                            long lastStamp = stamp(method.getLast());
                            long nowStamp = stamp(now);
                            long finalStamp = lastStamp;
                            if(lastStamp < nowStamp){
                                finalStamp = lastStamp + MAX_LIMIT_SECONDS;
                            }
                            if(lastStamp <= nowStamp && nowStamp <= finalStamp){
                                EXECUTOR.execute(o.getValue());
                                method.setLast(Crones.next(method.getJob().cron(),method.getLast(),false));
                            }
                        });
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();

    }

    private static long stamp(LocalDateTime time){
        return time.toEpochSecond(ZoneOffset.ofHours(8));
    }

    private final JobThreadPoolExecutor EXECUTOR
            = new JobThreadPoolExecutor(4,10,5,TimeUnit.SECONDS,new LinkedBlockingQueue<>());

    private class JobThreadPoolExecutor extends ThreadPoolExecutor{
        public JobThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.DiscardPolicy());
        }
        public void execute(final JobMethod jobMethod) {
            final Runnable command = () -> {
                NettyClient.getInstance().send(jobMethod.getJobName());
            };
            super.execute(command);
        }
    }

    private ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
}
